package cn.highset.redis.service;

import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * @author gg
 * @version RedisSetService.java, v 0.1 2019-04-11 9:51 gg
 */
public class RedisSetService extends RedisBaseService {

    private RedisTemplate<String, Object> redisTemplate;

    public RedisSetService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 对应 sAdd 命令
     * 将一个或多个 member 元素加入到集合 key 当中，已经存在于集合的 member 元素将被忽略。
     * 假如 key 不存在，则创建一个只包含 member 元素作成员的集合。
     * 当 key 不是集合类型时，返回一个错误
     *
     * @param key    must not be {@literal null}.
     * @param values 值
     * @return 被添加到集合中的新元素的数量，不包括被忽略的元素
     * @see <a href="http://redis.io/commands/sadd">Redis Documentation: SADD</a>
     */
    public Long sAdd(final String key, Object... values) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.add(key, values);
    }

    /**
     * 对应 sRem
     * 移除集合 key 中的一个或多个 member 元素，不存在的 member 元素会被忽略。
     * 当 key 不是集合类型，返回一个错误
     *
     * @param key    must not be {@literal null}.
     * @param values 值
     * @return 被成功移除的元素的数量，不包括被忽略的元素
     * @see <a href="http://redis.io/commands/srem">Redis Documentation: SREM</a>
     */
    public Long remove(final String key, Object... values) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.remove(key, values);
    }

    /**
     * 对应命令 sPop
     * 移除并返回集合中的一个随机元素。
     * 如果只想获取一个随机元素，但不想该元素从集合中被移除的话，可以使用 sRandMember key [count] 命令
     *
     * @param key must not be {@literal null}.
     * @return 被移除的随机元素。 当 key 不存在或 key 是空集时，返回 null
     * @see <a href="http://redis.io/commands/spop">Redis Documentation: SPOP</a>
     */
    public Object sPop(final String key) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.pop(key);
    }

    /**
     * 对应 sMove
     * 将 member 元素从 source 集合移动到 destination 集合
     * 如果 source 集合不存在或不包含指定的 member 元素，则 sMove 命令不执行任何操作，仅返回 0 。
     * 否则， member 元素从 source 集合中被移除，并添加到 destination 集合中去。
     * 当 destination 集合已经包含 member 元素时， sMove 命令只是简单地将 source 集合中的 member 元素删除。
     * 当 source 或 destination 不是集合类型时，返回一个错误
     *
     * @param key     must not be {@literal null}.
     * @param value   值
     * @param destKey must not be {@literal null}.
     * @return 如果 member 元素被成功移除，返回 1 。
     * 如果 member 元素不是 source 集合的成员，并且没有任何操作对 destination 集合执行，那么返回 0
     * @see <a href="http://redis.io/commands/smove">Redis Documentation: SMOVE</a>
     */
    public Boolean sMove(final String key, Object value, final String destKey) {
        checkKey(key);
        checkKey(destKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.move(key, value, destKey);
    }

    /**
     * 对应命令 sCard
     * 返回集合 key 的基数(集合中元素的数量)
     *
     * @param key must not be {@literal null}.
     * @return 集合的基数。 当 key 不存在时，返回 0
     * @see <a href="http://redis.io/commands/scard">Redis Documentation: SCARD</a>
     */
    public Long sCard(final String key) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.size(key);
    }

    /**
     * 对应命令 sIsMember
     * 判断 member 元素是否集合 key 的成员
     *
     * @param key    must not be {@literal null}.
     * @param member 成员
     * @return 如果 member 元素是集合的成员，返回 1 。 如果 member 元素不是集合的成员，或 key 不存在，返回 0
     * @see <a href="http://redis.io/commands/sismember">Redis Documentation: SISMEMBER</a>
     */
    public Boolean sIsMember(final String key, Object member) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.isMember(key, member);
    }

    /**
     * 对应命令 sInter
     * 返回一个集合的全部成员，该集合是所有给定集合的交集。
     * 不存在的 key 被视为空集。
     * 当给定集合当中有一个空集时，结果也为空集(根据集合运算定律)
     *
     * @param key      must not be {@literal null}.
     * @param otherKey must not be {@literal null}.
     * @return 交集成员的列表
     * @see <a href="http://redis.io/commands/sinter">Redis Documentation: SINTER</a>
     */
    public Set<Object> sInter(final String key, final String otherKey) {
        checkKey(key);
        checkOtherKey(otherKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.intersect(key, otherKey);
    }

    /**
     * 对应命令 sInter
     * 返回一个集合的全部成员，该集合是所有给定集合的交集。
     * 不存在的 key 被视为空集。
     * 当给定集合当中有一个空集时，结果也为空集(根据集合运算定律)
     *
     * @param key       must not be {@literal null}.
     * @param otherKeys must not be {@literal null}.
     * @return 交集成员的列表
     * @see <a href="http://redis.io/commands/sinter">Redis Documentation: SINTER</a>
     */
    public Set<Object> sInter(final String key, Collection<String> otherKeys) {
        checkKey(key);
        if (CollectionUtils.isEmpty(otherKeys)) {
            throw new IllegalArgumentException("key 集合不能为空");
        }
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.intersect(key, otherKeys);
    }

    /**
     * 对应命令 sInterStore
     * 这个命令类似于 sInter key [key …] 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 集合已经存在，则将其覆盖。
     * destination 可以是 key 本身
     *
     * @param key      must not be {@literal null}.
     * @param otherKey must not be {@literal null}.
     * @param destKey  must not be {@literal null}.
     * @return 结果集中的成员数量
     * @see <a href="http://redis.io/commands/sinterstore">Redis Documentation: SINTERSTORE</a>
     */
    public Long sInterStore(final String key, final String otherKey, final String destKey) {
        checkKey(key);
        checkOtherKey(otherKey);
        checkDestKey(destKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.intersectAndStore(key, otherKey, destKey);
    }

    /**
     * 对应命令 sInterStore
     * 这个命令类似于 sInter key [key …] 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 集合已经存在，则将其覆盖。
     * destination 可以是 key 本身
     *
     * @param key       must not be {@literal null}.
     * @param otherKeys must not be {@literal null}.
     * @param destKey   must not be {@literal null}.
     * @return 结果集中的成员数量
     * @see <a href="http://redis.io/commands/sinterstore">Redis Documentation: SINTERSTORE</a>
     */
    public Long sInterStore(final String key,
                            Collection<String> otherKeys,
                            final String destKey) {
        SetOperations<String, Object> setOperations = checkKeyOtherKeyDestKey(key, otherKeys, destKey);
        return setOperations.intersectAndStore(key, otherKeys, destKey);
    }

    /**
     * 对应命令 sUnion
     * 返回一个集合的全部成员，该集合是所有给定集合的并集。
     * 不存在的 key 被视为空集
     *
     * @param key      must not be {@literal null}.
     * @param otherKey must not be {@literal null}.
     * @return 并集成员的列表
     * @see <a href="http://redis.io/commands/sunion">Redis Documentation: SUNION</a>
     */
    public Set<Object> sUnion(final String key, final String otherKey) {
        checkKey(key);
        checkOtherKey(otherKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.union(key, otherKey);
    }

    /**
     * 对应命令 sUnion
     * 返回一个集合的全部成员，该集合是所有给定集合的并集。
     * 不存在的 key 被视为空集
     *
     * @param key       must not be {@literal null}.
     * @param otherKeys must not be {@literal null}.
     * @return 并集成员的列表
     * @see <a href="http://redis.io/commands/sunion">Redis Documentation: SUNION</a>
     */
    public Set<Object> sUnion(final String key,
                              Collection<String> otherKeys) {
        checkKey(key);
        if (CollectionUtils.isEmpty(otherKeys)) {
            throw new IllegalArgumentException("key 集合不能为空");
        }
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.union(key, otherKeys);
    }

    /**
     * 对应命令 sUnionStore
     * 这个命令类似于 sUnion key [key …] 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 已经存在，则将其覆盖。
     * destination 可以是 key 本身
     *
     * @param key      must not be {@literal null}.
     * @param otherKey must not be {@literal null}.
     * @param destKey  must not be {@literal null}.
     * @return 结果集中的元素数量
     * @see <a href="http://redis.io/commands/sunionstore">Redis Documentation: SUNIONSTORE</a>
     */
    public Long sUnionStore(final String key, final String otherKey, final String destKey) {
        checkKey(key);
        checkOtherKey(otherKey);
        checkDestKey(destKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.unionAndStore(key, otherKey, destKey);
    }

    /**
     * 对应命令 sUnionStore
     * 这个命令类似于 sUnion key [key …] 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 已经存在，则将其覆盖。
     * destination 可以是 key 本身
     *
     * @param key       must not be {@literal null}.
     * @param otherKeys must not be {@literal null}.
     * @param destKey   must not be {@literal null}.
     * @return 结果集中的元素数量
     * @see <a href="http://redis.io/commands/sunionstore">Redis Documentation: SUNIONSTORE</a>
     */
    public Long sUnionStore(final String key, Collection<String> otherKeys, final String destKey) {
        SetOperations<String, Object> setOperations = checkKeyOtherKeyDestKey(key, otherKeys, destKey);
        return setOperations.unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 对应命令 sDiff
     * 返回一个集合的全部成员，该集合是所有给定集合之间的差集。
     * 不存在的 key 被视为空集
     *
     * @param key      must not be {@literal null}.
     * @param otherKey must not be {@literal null}.
     * @return 一个包含差集成员的列表
     * @see <a href="http://redis.io/commands/sdiff">Redis Documentation: SDIFF</a>
     */
    public Set<Object> sDiff(final String key, final String otherKey) {
        checkKey(key);
        checkOtherKey(otherKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.difference(key, otherKey);
    }

    /**
     * 对应命令 sDiff
     * 返回一个集合的全部成员，该集合是所有给定集合之间的差集。
     * 不存在的 key 被视为空集
     *
     * @param key       must not be {@literal null}.
     * @param otherKeys must not be {@literal null}.
     * @return 一个包含差集成员的列表
     * @see <a href="http://redis.io/commands/sdiff">Redis Documentation: SDIFF</a>
     */
    public Set<Object> sDiff(final String key, Collection<String> otherKeys) {
        checkKey(key);
        if (CollectionUtils.isEmpty(otherKeys)) {
            throw new IllegalArgumentException("key 集合不能为空");
        }
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.difference(key, otherKeys);
    }

    /**
     * 对应命令 sDiffStore
     * 这个命令的作用和 sDiff key [key …] 类似，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 集合已经存在，则将其覆盖。
     * destination 可以是 key 本身
     *
     * @param key      must not be {@literal null}.
     * @param otherKey must not be {@literal null}.
     * @param destKey  must not be {@literal null}.
     * @return 结果集中的元素数量
     * @see <a href="http://redis.io/commands/sdiffstore">Redis Documentation: SDIFFSTORE</a>
     */
    public Long sDiffStore(final String key, final String otherKey, final String destKey) {
        checkKey(key);
        checkOtherKey(otherKey);
        checkDestKey(destKey);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.differenceAndStore(key, otherKey, destKey);
    }

    /**
     * 对应命令 sDiffStore
     * 这个命令的作用和 sDiff key [key …] 类似，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 集合已经存在，则将其覆盖。
     * destination 可以是 key 本身
     *
     * @param key       must not be {@literal null}.
     * @param otherKeys must not be {@literal null}.
     * @param destKey   must not be {@literal null}.
     * @return 结果集中的元素数量
     * @see <a href="http://redis.io/commands/sdiffstore">Redis Documentation: SDIFFSTORE</a>
     */
    public Long sDiffStore(final String key, Collection<String> otherKeys, final String destKey) {
        SetOperations<String, Object> setOperations = checkKeyOtherKeyDestKey(key, otherKeys, destKey);
        return setOperations.differenceAndStore(key, otherKeys, destKey);
    }

    private SetOperations<String, Object> checkKeyOtherKeyDestKey(String key, Collection<String> otherKeys, String destKey) {
        checkKey(key);
        checkDestKey(destKey);
        if (CollectionUtils.isEmpty(otherKeys)) {
            throw new IllegalArgumentException("key 集合不能为空");
        }
        return redisTemplate.opsForSet();
    }

    /**
     * 对应命令 sMembers
     * 返回集合 key 中的所有成员
     * 不存在的 key 被视为空集合
     *
     * @param key must not be {@literal null}.
     * @return 集合中的所有成员
     * @see <a href="http://redis.io/commands/smembers">Redis Documentation: SMEMBERS</a>
     */
    public Set<Object> sMembers(final String key) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.members(key);
    }

    /**
     * 对应命令 sRandMember
     * 如果命令执行时，只提供了 key 参数，那么返回集合中的一个随机元素
     *
     * @param key must not be {@literal null}.
     * @return 只提供 key 参数时，返回一个元素；如果集合为空，返回 null
     * @see <a href="http://redis.io/commands/srandmember">Redis Documentation: SRANDMEMBER</a>
     */
    public Object sRandMember(final String key) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.randomMember(key);
    }

    /**
     * 对应命令 sRandMember
     * 如果命令执行时，只提供了 key 参数，那么返回集合中的一个随机元素
     * 从 Redis 2.6 版本开始， sRandMember 命令接受可选的 count 参数：
     * 1.如果 count 为正数，且小于集合基数，那么命令返回一个包含 count 个元素的数组，数组中的元素各不相同。如果 count 大于等于集合基数，那么返回整个集合。
     * 2.如果 count 为负数，那么命令返回一个数组，数组中的元素可能会重复出现多次，而数组的长度为 count 的绝对值。
     * 该操作和 sPop key 相似，但 sPop key 将随机元素从集合中移除并返回，而 sRandMember 则仅仅返回随机元素，而不对集合进行任何改动
     *
     * @param key   must not be {@literal null}.
     * @param count 数量
     * @return 返回一个数组；如果集合为空，返回空数组
     * @see <a href="http://redis.io/commands/srandmember">Redis Documentation: SRANDMEMBER</a>
     */
    public Set<Object> distinctRandomMembers(final String key, long count) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.distinctRandomMembers(key, count);
    }

    /**
     * 对应命令 sRandMember
     * 如果命令执行时，只提供了 key 参数，那么返回集合中的一个随机元素
     * 从 Redis 2.6 版本开始， sRandMember 命令接受可选的 count 参数：
     * 1.如果 count 为正数，且小于集合基数，那么命令返回一个包含 count 个元素的数组，数组中的元素各不相同。如果 count 大于等于集合基数，那么返回整个集合。
     * 2.如果 count 为负数，那么命令返回一个数组，数组中的元素可能会重复出现多次，而数组的长度为 count 的绝对值。
     * 该操作和 sPop key 相似，但 sPop key 将随机元素从集合中移除并返回，而 sRandMember 则仅仅返回随机元素，而不对集合进行任何改动
     *
     * @param key   must not be {@literal null}.
     * @param count 数量
     * @return 返回一个数组；如果集合为空，返回空数组
     * @see <a href="http://redis.io/commands/srandmember">Redis Documentation: SRANDMEMBER</a>
     */
    public List<Object> randomMembers(final String key, long count) {
        checkKey(key);
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.randomMembers(key, count);
    }

    /**
     * 对应命令 sScan
     *
     * @param key     key
     * @param pattern 规则
     * @param limit   数量
     * @return Cursor
     * @since 1.4
     */
    public Cursor<Object> sScan(final String key, final String pattern, long limit) {
        checkKey(key);
        checkPattern(pattern);
        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(limit).build();
        SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
        return setOperations.scan(key, options);
    }

}